#!python
#
# Info for generating core classes
#
#import pyplusplus.module_builder.call_policies as cp
from pyplusplus.module_builder.call_policies import *
import pygccxml.declarations.type_traits as tt
import settings   
from goodies import *
import pygccxml.declarations as pd

# ------------ Data output and generation --------------- #
# Idea: Generate this from doxygen or opensg fcd files

class ClassInfo:
   def __init__(self, name, excludeList=[], policyList=[], includes=[],
                useSharedPtr=False, declarationCode = [],
                registrationCode = [], modCallable = None, finalize = False):
      self.name = name
      self.excludes = excludeList
      self.policies = policyList
      self.includes = includes
      self.use_shared_ptr = useSharedPtr
      self.declarations = declarationCode
      self.registrations = registrationCode
      self.modCallable   = modCallable         # Called as callable(class)
      self.finalize      = finalize

gen_classes = []

# Add basic classes that can just be wrapped with no customization
for c in ["ActionBase", 
          "SimpleSceneManager", 
          "Navigator", "FlyNavigator", "TrackballNavigator", "WalkNavigator",
          "Connection", "PointConnection", "GroupConnection", "ConnectionType",
          "ClusterNetwork", "ClusterServer", "DrawActionBase",
          "RenderTraversalActionBase", "TreeBuilderBase"]:
   ci = ClassInfo(c)
   gen_classes.append(ci)

# For OSG::ExceptionBinaryDataHandler::petValues(), we could set things up so
# that the input is a Python list. From there, however, it becomes tricky
# because we need to know the data type in the list in order to get the
# correct overload of OSG::ExceptionBinaryDataHandler::petValues().
gen_classes.append(
   ClassInfo("ExceptionBinaryDataHandler",
             excludeList = ["put", "putAndFree", "putValues", "get",
                            "getAndAlloc", "getValues"])
)

# Exclude begin, beginCreated, end, endCreated because they return
# non-portable iterators.
gen_classes.append(
   ClassInfo("ChangeList",
             excludeList = ['begin', 'beginCreated', 'end', 'endCreated'],
             policyList = [("create", return_value_policy(reference_existing_object))])
)

action_callback_decl_code = """
namespace
{

OSG::ActionBase::ResultE invokeCallback(bp::object obj,
                                        OSG::NodeCorePtrConstArg core,
                                        OSG::Action* action)
{
   return bp::extract<OSG::ActionBase::ResultE>(obj(core, action));
}

}
"""

action_static_decl_code = """
namespace
{

void wrapRegisterEnterDefault(const OSG::FieldContainerType& type,
                              bp::object func)
{
   OSG::%(Action)s::registerEnterDefault(type, boost::bind(invokeCallback, func, _1, _2));
}

void wrapRegisterLeaveDefault(const OSG::FieldContainerType& type,
                              bp::object func)
{
   OSG::%(Action)s::registerLeaveDefault(type, boost::bind(invokeCallback, func, _1, _2));
}

}
"""

action_mem_decl_code = """
namespace
{

void wrapRegisterEnterFunction(OSG::Action* self,
                               const OSG::FieldContainerType& type,
                               bp::object func)
{
   self->registerEnterFunction(type,
                               boost::bind(invokeCallback, func, _1, _2));
}

void wrapRegisterLeaveFunction(OSG::Action* self,
                               const OSG::FieldContainerType& type,
                               bp::object func)
{
   self->registerLeaveFunction(type,
                               boost::bind(invokeCallback, func, _1, _2));
}

}
"""

action_static_reg_code = [
   'def("registerEnterDefault", wrapRegisterEnterDefault, (bp::arg("type"), bp::arg("func")))',
   'def("registerLeaveDefault", wrapRegisterLeaveDefault, (bp::arg("type"), bp::arg("func")))',
]

action_mem_reg_code = [
   'def("registerEnterFunction", wrapRegisterEnterFunction, (bp::arg("type"), bp::arg("func")))',
   'def("registerLeaveFunction", wrapRegisterLeaveFunction, (bp::arg("type"), bp::arg("func")))',
]

ac = ClassInfo("Action",
               ["registerEnterDefault","registerEnterFunction",
                "registerLeaveDefault","registerLeaveFunction"],
               declarationCode = [action_callback_decl_code,
                                  action_static_decl_code % {'Action' : 'Action'},
                                  action_mem_decl_code],
               registrationCode = action_static_reg_code + action_mem_reg_code,
               useSharedPtr = False)
gen_classes.append(ac)

ac = ClassInfo("RenderTraversalAction",
               ["dropFunctor","registerEnterDefault","registerLeaveDefault"],
               declarationCode = [action_callback_decl_code,
                                  action_static_decl_code % {'Action' : 'RenderTraversalAction'}],
               registrationCode = action_static_reg_code)
gen_classes.append(ac)

ac = ClassInfo("RenderAction", 
               ["dropFunctor","registerEnterDefault","registerLeaveDefault"], 
               [], [],
               declarationCode = [action_callback_decl_code,
                                  action_static_decl_code % {'Action' : 'RenderAction'}],
               registrationCode = action_static_reg_code)
gen_classes.append(ac)

ac = ClassInfo("IntersectAction",
               ["registerEnterDefault", "registerLeaveDefault"], 
               [], [],
               declarationCode = [action_callback_decl_code,
                                  action_static_decl_code % {'Action' : 'IntersectAction'}],
               registrationCode = action_static_reg_code)
gen_classes.append(ac)

rp = ClassInfo("RenderPartition", ["dropFunctor"])
gen_classes.append(rp)

# Exclude OSG::SceneFileType::read() and OSG::SceneFileType::write() because
# they require a C++ stream object as an argument. We could probably devise a
# wrapper that maps between a Python list and a C++ stream, though.
ac = ClassInfo("SceneFileType", excludeList = ["read", "write", "suffixList"])
gen_classes.append(ac)


def statCollectorCustomizations(klass):
   """ Remove all the operators. """
   cmethods = klass.calldefs()
   for m in cmethods:
      if isinstance(m, operator_t) and m.symbol in ["+","+="]:
         m.exclude()   

sc = ClassInfo("StatCollector",
               ["StatCollector", "getElem", "reset", "getValue"],
               [("create", return_value_policy(reference_existing_object))],
               modCallable=statCollectorCustomizations)
gen_classes.append(sc)

ra_decl_code = """
namespace
{

bool invokeCallback(bp::object obj, OSG::FieldContainerPtrConstArg fc,
                    OSG::RemoteAspect* ra)
{
   return bp::extract<bool>(obj(fc, ra));
}

void wrapRegisterCreated(OSG::RemoteAspect* self,
                         const OSG::FieldContainerType& type, bp::object func)
{
   self->registerCreated(type, boost::bind(invokeCallback, func, _1, _2));
}

void wrapRegisterDestroyed(OSG::RemoteAspect* self,
                           const OSG::FieldContainerType& type,
                           bp::object func)
{
   self->registerDestroyed(type, boost::bind(invokeCallback, func, _1, _2));
}

void wrapRegisterChanged(OSG::RemoteAspect* self,
                         const OSG::FieldContainerType& type, bp::object func)
{
   self->registerChanged(type, boost::bind(invokeCallback, func, _1, _2));
}

}
"""

ra_reg_code = [
   'def("registerCreated", wrapRegisterCreated, (bp::arg("type"), bp::arg("func")))',
   'def("registerDestroyed", wrapRegisterDestroyed, (bp::arg("type"), bp::arg("func")))',
   'def("registerChanged", wrapRegisterChanged, (bp::arg("type"), bp::arg("func")))',
]

ra = ClassInfo("RemoteAspect",
               excludeList = ["registerCreated", "registerDestroyed",
                              "registerChanged"],
               declarationCode = [ra_decl_code],
               registrationCode = ra_reg_code)
gen_classes.append(ra)

# OSG::Thread is exposed so that we can get access to the change list for a
# given thread, not so that Python programmers can spawn OpenSG threads. If it
# is deemed necessary that Python programmers be able to do this, then some
# further special consideration will probably be needed for this class. In
# particular, OGS::Thread::runFunction() would need to handle accepting a
# Python callable. This may not be so easy, however, as OSG::Thread does not
# use an instance of boost::function<T> as the thread callback type.
t = ClassInfo("Thread", ["runFunction"],
              [("find", return_value_policy(reference_existing_object)),
               ("getCurrentChangeList", return_value_policy(reference_existing_object))])
gen_classes.append(t)

rtn = ClassInfo("RenderTreeNode", excludeList = ["getFunctor", "setFunctor"])
gen_classes.append(rtn)

# Instead of excluding begin() and end() for OSG::StateOverride's ChunkStore,
# it would be nice to have a property such as "chunks" that returns an
# iterator.
so = ClassInfo("StateOverride", excludeList = ["begin", "end"])
gen_classes.append(so)

# --- Node Cores and other subclasses of OSG::FieldContainer ------ #

class CoreInfo:
   def __init__(self, name, parent, excludeList=[], policyList=[], includes=[], 
                hasBase=True, addMethods = [], templateMethods=[], templateInstances=[],
                documentation=""):
      " If parent == None, then no parent. "
      self.name = name
      self.parent = parent
      self.excludes = excludeList
      self.policies = policyList
      self.includes = includes
      self.hasBase = hasBase
      self.addMethods = addMethods
      self.templateMethods = templateMethods  # A list of methods to treat as template methods
      self.templateInstances = templateInstances  # A list of methods to force instantiation of
      self.documentation = documentation

# Map from class name to CoreInfo class
core_infos = {}

# fc's that are custom (not in fcd files)
aci = CoreInfo("AttachmentContainer", "FieldContainer", hasBase=False)
core_infos["AttachmentContainer"] = aci

# Base class for state chunks and other things
fca = CoreInfo("FieldContainerAttachment", "FieldContainer", hasBase=False)
core_infos["FieldContainerAttachment"] = fca

ni = CoreInfo("Node", "AttachmentContainer", hasBase=False)
core_infos["Node"] = ni

nci = CoreInfo("NodeCore", "AttachmentContainer", hasBase=False)
core_infos["NodeCore"] = nci

# OSG::CameraDecorator is a rare field container type that does not have a
# .fcd file.
camera_decorator_info = CoreInfo("CameraDecorator", "Camera")
core_infos["CameraDecorator"] = camera_decorator_info

# Field containers that need customization.
image_info = CoreInfo("Image","AttachmentContainer",
                      ["store","restore","getData","getDataByTime","editDataByTime","calcMipmapGeometry",
                       "clearHalf"])
core_infos["Image"] = image_info

window_info = CoreInfo("Window","AttachmentContainer",
                      ["getFunction","getFunctionNoCheck","getFunctionByName",
                       "destroyGLObject","getGLObjectsSize","refreshAllGLObjects",
                       "refreshGLObject","registerGLObject","reinitializeAllGLObjects",
                       "reinitializeGLObject",
                       "registerExtension","registerFunction"])
core_infos["Window"] = window_info

#gp_info = CoreInfo("GeoProperty", "StateChunk",
#                   ["getData"])
#core_infos["GeoProperty"] = gp_info

# Build up list of template methods that we need to instantiate so we can get all of them exposed.
gvp_tmpl_instances = ["template %s OSG::GeoVectorProperty::getValue<%s>(const UInt32) const"%(t,t) for t in settings.geo_vec_get_value_ret_types]
gvp_tmpl_instances.extend(["template void OSG::GeoVectorProperty::setValue<%s>(const %s&, const UInt32)"%(t,t) for t in settings.geo_vec_get_value_ret_types])
gvp_tmpl_instances.extend(["template void OSG::GeoVectorProperty::addValue<%s>(const %s&)"%(t,t) for t in settings.geo_vec_get_value_ret_types])
gvp_tmpl_instances.extend(["template void OSG::GeoVectorProperty::push_back<%s>(const %s&)"%(t,t) for t in settings.geo_vec_get_value_ret_types])
                          
# Expose the GeoVectorProperty class
# - The getValue stuff will trigger code that renames the overloaded template methods to name name like getValue_Color3f, etc
# - The tmpl_instances are used to make sure that the compiler sees all the versions of these overloaded methods that we need
gvp = CoreInfo("GeoVectorProperty", "GeoProperty",
         templateMethods = ["getValue",],
         templateInstances = gvp_tmpl_instances)
core_infos[gvp.name] = gvp

# Build up list of template methods that we need to instantiate so we can get all of them exposed.
gip_tmpl_instances = ["template %s OSG::GeoIntegralProperty::getValue<%s>(const UInt32) const"%(t,t) for t in settings.geo_integral_types]
gip_tmpl_instances.extend(["template void OSG::GeoIntegralProperty::setValue<%s>(const %s&, const UInt32)"%(t,t) for t in settings.geo_integral_param_types])
gip_tmpl_instances.extend(["template void OSG::GeoIntegralProperty::addValue<%s>(const %s&)"%(t,t) for t in settings.geo_integral_param_types])
gip_tmpl_instances.extend(["template void OSG::GeoIntegralProperty::push_back<%s>(const %s&)"%(t,t) for t in settings.geo_integral_param_types])

# Expose all the real versions we want


# Expose the GeoIntegralProperty class
# - The getValue stuff will trigger code that renames the overloaded template methods to name name like getValue_Color3f, etc
# - The tmpl_instances are used to make sure that the compiler sees all the versions of these overloaded methods that we need
gip = CoreInfo("GeoIntegralProperty", "GeoProperty",
         templateMethods = ["getValue",],
         templateInstances = gip_tmpl_instances)
core_infos[gip.name] = gip


core_ignore_list = ["ImageComposer","DisplayCalibration", "SortFirstWindow",
                    "SortLastWindow", "SepiaComposer", "BinarySwapComposer",
                    "PipelineComposer","ParallelComposer","BalancedMultiWindow",
                    "DisplayFilterForeground",
                    "ShadowMapEngine","SimpleShadowMapEngine"]

def modGeoVectorProperty(cls):
   pass
   # Exclude get values that return by ref
   #cls.member_functions("getValue",arg_types=[None,None]).exclude()
   
   # Exclude default setValue
   #cls.member_functions(calldef_matcher("setValue")&virtuality_type_matcher(VIRTUALITY_TYPES.PURE_VIRTUAL)).exclude()
   
   


# Map of core id to callables to call on (class, base) decl
core_mods = {}
core_mods["Slices"] = [ [lambda c: c["Edge"].exclude(), 
                         lambda c: c["Slice"].exclude(),
                         lambda c: c["createSlice"].exclude(),
                         lambda c: c["drawSlices"].exclude()],
                        [] ]
#core_mods["Image"] = [ [lambda c: c["createMipmap"].use_overload_macro = True, ], []]
core_mods[gvp.name] = [ [modGeoVectorProperty,],[]]


# ---- Methods ----- #

public_methods = ["calcVertexNormals", "calcVertexNormalsGeo", "calcFaceNormalsGeo",
                  "getDefaultMaterial","getDefaultUnlitMaterial",
                  "makePlane","makePlaneGeo",
                  "makeCone","makeConeGeo",
                  "makeCylinder","makeCylinderGeo",
                  "makeConicalFrustum","makeConicalFrustumGeo",
                  "makeTorus","makeTorusGeo",
                  "makeSphere","makeSphereGeo",
                  "makeLatLongSphere","makeLatLongSphereGeo",
                  "makeBox","makeBoxGeo",
                  "makeCoordAxis", "makeCoordAxisGeo",
                  "makeTeapot","makeTeapotGeo",
                  "setName", "getName", "stringAttributeMap",
                  "cloneTree", "deepCloneTree",
                  "commitChanges"]

# Map of public method names to callables to call on the methods decls
public_method_mods = {}

def setUseOverloadMacro(m):
   m.use_overload_macro = True

# gccxml bug causes problems with default arguments on these methods.
for m in ["cloneTree","deepCloneTree"]:
   public_method_mods[m] = [ setUseOverloadMacro ]

